Skip to content

fix: re-read settings when CACHEKIT_MASTER_KEY appears + correct missing-key env var name#200

Merged
27Bslash6 merged 2 commits into
mainfrom
fix/195-194-encryption-config
Jun 26, 2026
Merged

fix: re-read settings when CACHEKIT_MASTER_KEY appears + correct missing-key env var name#200
27Bslash6 merged 2 commits into
mainfrom
fix/195-194-encryption-config

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Two encryption-config correctness fixes.

Closes #195. Closes #194.

#195 — keyless settings singleton froze permanently

get_settings() built CachekitConfig once and cached it process-wide. If the first build happened before CACHEKIT_MASTER_KEY was in the environment — e.g. an import-time @cache/@cache.io decorator evaluated before the app loads its secrets (python-dotenv, cloud secret managers) — it froze master_key=None forever. Setting the env var afterward had no effect: with auto-encryption that means data is cached plaintext though a key is present, and with explicit @cache.secure it surfaces as #194's confusing "master key required".

Fix: get_settings() now re-reads once the key appears (master_key is None and CACHEKIT_MASTER_KEY now set → rebuild, under the existing lock). Idempotent — after the rebuild master_key is set, so it never fires again (no per-call churn).

#194 — missing-key error named a nonexistent env var

The error and a master_key docstring told users to set REDIS_CACHE_MASTER_KEY, which is never read; the only honored variable is CACHEKIT_MASTER_KEY. Corrected both strings (encryption_wrapper.py + cache_handler.py), matching the already-correct sibling message in config/nested.py.

Scope / known residual

The self-heal fixes the singleton and any handler built after the key loads. A CacheSerializationHandler is built eagerly when @cache is applied (decoration/import) and freezes its encryption decision at __init__; a handler frozen keyless at import is not retroactively unfrozen by this change. Fully closing that (auto-encryption + import-time decorator + secrets-after-import) needs lazy encryption resolution in the handler — invasive, security-critical, deferred to its own PR. Note @cache.secure (explicit) already fails loud in that window, so only the auto-encryption path is silent.

Tests

New tests/unit/test_encryption_config_resolution.py: get_settings() re-reads when the key appears (the #195 repro), stays stable once loaded, and the missing-key error names CACHEKIT_MASTER_KEY. Full suite: 1866 passed, basedpyright 0 errors.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Encryption configuration now automatically reinitialises when the required master key environment variable becomes available after startup, eliminating the need for manual reset operations.
  • Documentation & Error Handling

    • Updated encryption error messaging to correctly reference CACHEKIT_MASTER_KEY when the master key is missing.

…ing-key env var name

#195: get_settings() cached the config once; if first built before CACHEKIT_MASTER_KEY was in the environment (e.g. an import-time cache decorator before secrets load), it froze master_key=None forever — encryption then silently never activated. get_settings() now re-reads once the key appears (under the existing lock; idempotent, no churn after).

#194: the missing-key error and a docstring named REDIS_CACHE_MASTER_KEY, which is never read; corrected both to CACHEKIT_MASTER_KEY (the only honored var), matching config/nested.py.

Closes #194. Closes #195.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d34e27b6-1d60-495a-a8a8-962624be614a

📥 Commits

Reviewing files that changed from the base of the PR and between da81c09 and e22c3f7.

📒 Files selected for processing (1)
  • src/cachekit/config/singleton.py

Walkthrough

Corrects REDIS_CACHE_MASTER_KEY to CACHEKIT_MASTER_KEY in the EncryptionError message and CacheSerializationHandler docstring. Extends get_settings() with a double-checked locking self-heal that rebuilds the CachekitConfig singleton when master_key was cached as None but CACHEKIT_MASTER_KEY is now present in the environment. Adds a new unit test module validating both behaviours.

Changes

Master key env var fix and singleton self-heal

Layer / File(s) Summary
Correct CACHEKIT_MASTER_KEY references in error messages and docs
src/cachekit/serializers/encryption_wrapper.py, src/cachekit/cache_handler.py
_setup_encryption raises EncryptionError naming CACHEKIT_MASTER_KEY, and CacheSerializationHandler.__init__ docstring references the same env var.
get_settings() self-heal under double-checked locking
src/cachekit/config/singleton.py
Adds import os and a double-checked lock block in the get_settings() fast-path that detects master_key=None with CACHEKIT_MASTER_KEY present in the environment and rebuilds the singleton via CachekitConfig.from_env().
Unit tests for self-heal and correct error message
tests/unit/test_encryption_config_resolution.py
Adds TestKeylessSingletonSelfHeals covering the None-then-set and already-present-key scenarios, and TestMissingKeyErrorNamesCorrectEnvVar asserting the EncryptionError message names CACHEKIT_MASTER_KEY and not REDIS_CACHE_MASTER_KEY.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • cachekit-io/cachekit-py#133: Updates test fixtures to call reset_settings() and prevent CACHEKIT_MASTER_KEY environment leaks between test runs, directly interacting with the singleton get_settings() and master-key resolution logic modified in this PR.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the two main changes: singleton re-read behaviour and correction of environment variable naming.
Description check ✅ Passed The description thoroughly covers motivation, scope, and testing, though it doesn't strictly follow all template sections like explicit type-of-change checkboxes.
Linked Issues check ✅ Passed All changes directly address the objectives from #195 (singleton re-read mechanism) and #194 (corrected env var names in messages).
Out of Scope Changes check ✅ Passed All code changes are scoped to fixing #195 and #194; no unrelated modifications detected in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/195-194-encryption-config

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cachekit/config/singleton.py`:
- Around line 49-60: The code has a race condition where another thread calling
reset_settings() between the unlocked check on line 55 and the locked check on
line 59 could set _settings_instance to None, causing an AttributeError when
trying to access _settings_instance.master_key. Capture _settings_instance into
a local variable before the initial unlocked check, then use that cached
reference throughout the condition to prevent the instance from becoming None
between the two checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bd606741-8805-4d1f-81ab-56cd841ff3cf

📥 Commits

Reviewing files that changed from the base of the PR and between 9749121 and da81c09.

📒 Files selected for processing (4)
  • src/cachekit/cache_handler.py
  • src/cachekit/config/singleton.py
  • src/cachekit/serializers/encryption_wrapper.py
  • tests/unit/test_encryption_config_resolution.py

Comment thread src/cachekit/config/singleton.py
@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/cachekit/config/singleton.py 88.88% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

…ncurrent-reset AttributeError)

Addresses CodeRabbit on #200: the #195 self-heal dereferenced _settings_instance.master_key in the unlocked fast path after a non-None check. A concurrent reset_settings() between the two could null the global and AttributeError (or return None). Snapshot the global to a local for the unlocked checks; re-read it under the lock (rebuilding if a peer reset or it is still keyless).
@27Bslash6 27Bslash6 merged commit 12c9fff into main Jun 26, 2026
32 checks passed
@27Bslash6 27Bslash6 deleted the fix/195-194-encryption-config branch June 26, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant